﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using Kay.Communication.Framework.Common;
using Kay.Communication.Framework.Custom_EventArgs;

namespace Kay.Communication.Framework
{
    /// <summary>
    /// Tcp 类型的客户端
    /// </summary>
    public class TcpInterNetworkStreamClient
    {
        /// <summary>
        /// 基础通信客户端
        /// </summary>
        private BaseCommunication Client { get; set; }

        /// <summary>
        /// 获取或设置当前的用户信息
        /// </summary>
        public ContactsInfo OwnUserInfo { get; private set; }

        /// <summary>
        /// 获取或设置通信需要的参数信息
        /// </summary>
        public CommunicationArgs CommunicationArgs { get; private set; }

        /// <summary>
        /// 获取或设置文件传送列表
        /// </summary>
        public List<TransferFileInfo> TransferFileInfos { get; private set; }

        /// <summary>
        /// 获取联系人列表
        /// </summary>
        public List<ContactsInfo> Contacts { get { return Client == null ? null : Client.Contacts; } }

        /// <summary>
        /// 获取或设置默认文件保存路径
        /// </summary>
        public string DefaultPath { get; private set; }

        /// <summary>
        /// 初始化 Tcp 类型的 Socket 客户端
        /// </summary>
        /// <param name="name">用户名</param>
        /// <param name="pwd">用户密码</param>
        /// <param name="ip">服务器地址</param>
        /// <param name="port">服务器端口号</param>
        /// <exception cref="ArgumentException">当 name 为 String.Empty 或全部由空格组成时引发异常</exception>
        /// <exception cref="ArgumentNullException">当 name 为 NULL 时引发异常</exception>
        public TcpInterNetworkStreamClient(string name, string pwd, string ip, ushort port)
        {
            if (name == null) throw new ArgumentNullException("name");
            if (name.Trim() == string.Empty) throw new ArgumentException("name");
            Client = new BaseCommunication();
            InitializeEvents();
            CommunicationArgs = new CommunicationArgs
            {
                Encoding = Encoding.UTF8,
                AddressFamily = AddressFamily.InterNetwork,
                EndPoint = new IPEndPoint(IPAddress.Parse(ip), port),
                MessageLength = 1024 * 1000,
                ProtocolType = ProtocolType.Tcp,
                CommunicationMode = CommunicationModes.TcpClient,
                SocketType = SocketType.Stream,
                Waiting = 10000
            };
            Client.InitializeBaseCommunication(CommunicationArgs);

            DefaultPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Temp Files");
            if (!Directory.Exists(DefaultPath))
            {
                Directory.CreateDirectory(DefaultPath);
            }
            if (OwnUserInfo == null)
            {
                OwnUserInfo = new ContactsInfo();
            }
            OwnUserInfo.Name = name;
            OwnUserInfo.Password = pwd;
            OwnUserInfo.Id = Helpers.GetGuidToN();
            OwnUserInfo.HostName = Dns.GetHostName();
            if (TransferFileInfos == null)
                TransferFileInfos = new List<TransferFileInfo>();
        }

        /// <summary>
        /// 初始化事件
        /// </summary>
        private void InitializeEvents()
        {
            Client.TextMessageReceived += OnClientReciveMessage;
            Client.UpdateContactsComplete += OnClientUpdateContactsComplete;
            Client.RequestSendFile += OnClientRequestSendFile;
            Client.AgreeReceiveFile += OnClientAgreeReciveFile;
            Client.IsRuningChanged += OnClientIsRuningChanged;
            Client.ContactsConnected += OnClientContactsConnected;
            Client.ContactsOffline += OnClientContactsOffline;
            Client.TransferFileUserOffline += OnClientTransferFileUserOffline;
            Client.BroadcastTextMessageReceived += OnClientBroadcastTextMessageReceived;
        }

        private void OnClientBroadcastTextMessageReceived(object sender, TextMessageReceivedEventArgs e)
        {
            if (BroadcastTextMessageReceived != null)
                BroadcastTextMessageReceived.Invoke(this, e);
        }

        private void OnClientTransferFileUserOffline(object sender, ContactsInfoEventArgs e)
        {
            var fileclient = TransferFileInfos.Find(f => f.SendId == e.UserInfo.Id);

            if (fileclient == null || fileclient.IsOver) return;

            fileclient.IsOver = true;
            fileclient.OverTime = DateTime.Now;

            if (TransferFileUserOffline != null)
                TransferFileUserOffline.Invoke(this, e);
        }

        private void OnClientContactsOffline(object sender, ContactsInfoEventArgs e)
        {
            if (ContactsOffline != null)
                ContactsOffline.Invoke(this, e);
        }

        private void OnClientContactsConnected(object sender, ContactsInfoEventArgs e)
        {
            if (ContactsConnected != null)
                ContactsConnected.Invoke(this, e);
        }

        private void OnClientIsRuningChanged(bool obj)
        {
            if (IsRuningChanged != null)
                IsRuningChanged.Invoke(obj);
        }

        private void OnClientAgreeReciveFile(object sender, FileMessageReceivedEventArgs e)
        {
            if (ContactsAgreeReciveFile != null)
            {
                ContactsAgreeReciveFile.Invoke(this, e);
            }
            var sendFile = TransferFileInfos.Find(f => f.SendId == e.TransferFileInfo.SendId);
            if (!e.TransferFileInfo.Agree)
            {
                // 拒绝接受文件
                sendFile.IsOver = true;
                sendFile.OverTime = DateTime.Now;
                return;
            }

            var transferFileInfo = e.TransferFileInfo;
            using (var fileStream = new FileStream(transferFileInfo.SourceFileName, FileMode.Open, FileAccess.Read))
            {
                var lengthFile = fileStream.Length;
                var lengthBytesSend = 1024L * 512;
                if (lengthBytesSend >= lengthFile)
                {
                    var bytesSend = new byte[lengthFile];
                    fileStream.Read(bytesSend, 0, (int)lengthFile);
                    sendFile.Client.SendFileMessage(e.UserInfo.Id, OwnUserInfo.Id, transferFileInfo, bytesSend);
                    if (SendFileProgressChanged != null)
                    {
                        SendFileProgressChanged.Invoke(this, new ProgressIsChangedEventArgs(100, e.TransferFileInfo));
                    }
                }
                else
                {
                    while (fileStream.Position < fileStream.Length)
                    {
                        if (fileStream.Length - fileStream.Position < lengthBytesSend)
                        {
                            lengthBytesSend = fileStream.Length - fileStream.Position;
                        }
                        var bytesSend = new byte[lengthBytesSend];
                        fileStream.Read(bytesSend, 0, (int)lengthBytesSend);
                        e.TransferFileInfo.CurrentSize = fileStream.Position;
                        sendFile.Client.SendFileMessage(e.UserInfo.Id, OwnUserInfo.Id, transferFileInfo, bytesSend);
                        var speed = (decimal)e.TransferFileInfo.CurrentSize / e.TransferFileInfo.Size * 100;
                        if (SendFileProgressChanged != null)
                        {
                            SendFileProgressChanged.Invoke(this, new ProgressIsChangedEventArgs((int)speed, e.TransferFileInfo));
                        }
                        Thread.Sleep(10);
                    }
                }
            }

            sendFile.IsOver = true;
            sendFile.OverTime = DateTime.Now;
            if (SendFileComplete != null)
            {
                SendFileComplete.Invoke(this, e);
            }
        }

        private void OnClientRequestSendFile(object sender, FileMessageReceivedEventArgs e)
        {
            if (e.TransferFileInfo.SourceFileName == null)
            {
                e.TransferFileInfo.Agree = false;
                Client.SendAgreeReceiveFileMessage(e.UserInfo.Id, OwnUserInfo.Id, e.TransferFileInfo);
                return;
            }
            if (RequestSendFile != null)
            {
                if (RequestSendFile.Invoke(this, e))
                {
                    var transferFileClientId = Helpers.GetGuidToN();
                    e.TransferFileInfo.ReceiveId = transferFileClientId;
                    TransferFileInfos.Add(e.TransferFileInfo);

                    ThreadPool.QueueUserWorkItem(CreateTransferFileClientForRecive, transferFileClientId);
                }
                else
                {
                    e.TransferFileInfo.Agree = false;
                    Client.SendAgreeReceiveFileMessage(e.UserInfo.Id, OwnUserInfo.Id, e.TransferFileInfo);
                }
            }
        }

        private void CreateTransferFileClientForRecive(object obj)
        {
            var transferFileClientId = (string)obj;
            var transferFileInfo = TransferFileInfos.Find(t => t.ReceiveId == transferFileClientId);

            var transferFileClient = new BaseCommunication();

            transferFileInfo.Client = transferFileClient;
            transferFileInfo.FileName = Path.Combine(DefaultPath, Path.GetFileName(transferFileInfo.SourceFileName));
            transferFileInfo.Agree = true;

            if (File.Exists(transferFileInfo.FileName))
            {
                File.Delete(transferFileInfo.FileName);
            }

            transferFileClient.FileMessageReceived += OnTransferFileClientFileMessageRecived;
            transferFileClient.OwnUserInfoReceived += OnTransferFileClientOwnUserInfoReceivedForReceive;

            transferFileClient.InitializeBaseCommunication(CommunicationArgs);

            transferFileClient.Connect();

            transferFileClient.SendUserInfoMessage(new ContactsInfo { Id = transferFileClientId, Name = OwnUserInfo.Id, TransferFile = true, LoginTime = DateTime.Now });
        }

        private void OnTransferFileClientOwnUserInfoReceivedForReceive(string obj)
        {
            var transferFileInfo = TransferFileInfos.Find(f => f.ReceiveId == obj);
            if (transferFileInfo != null)
            {
                Client.SendAgreeReceiveFileMessage(transferFileInfo.SendUserId, transferFileInfo.ReceiveUserId, transferFileInfo);
            }
        }

        /// <summary>
        /// 启动客户端
        /// </summary>
        public bool Start()
        {
            if (!Client.Connect())
                return false;
            OwnUserInfo.LocalEndPointByClient = Client.GetSocketLocalEndPoint();
            OwnUserInfo.RemoteEndPointByClient = Client.GetSocketRemoteEndPoint();
            OwnUserInfo.LoginTime = DateTime.Now;
            Client.SendUserInfoMessage(OwnUserInfo);
            ThreadPool.QueueUserWorkItem(CleanTransferFileInfos);
            return true;
        }

        /// <summary>
        /// 清理传输文件信息列表
        /// </summary>
        /// <param name="obj"></param>
        private void CleanTransferFileInfos(object obj)
        {
            while (true)
            {
                Thread.Sleep(1000 * 60);
                try
                {
                    if (TransferFileInfos != null && TransferFileInfos.Count > 0)
                    {
                        var overs = TransferFileInfos.Where(o => o.IsOver).ToList();
                        if (overs.Count > 0)
                        {
                            foreach (var over in overs)
                            {
                                if (DateTime.Now - over.OverTime <= TimeSpan.FromMinutes(3)) continue;
                                over.Client.Close();
                                TransferFileInfos.Remove(over);
                            }
                        }
                    }
                }
                catch (Exception e)
                {
                    Console.WriteLine(e);
                    throw;
                }
                GC.Collect();
            }
        }

        /// <summary>
        /// 向指定用户发送消息
        /// </summary>
        /// <param name="receiveId">接收用户，Null 表示服务器</param>
        /// <param name="text">消息内容</param>
        /// <returns>True 发送成功，否则为 False。</returns>
        public bool SendMessage(string receiveId, string text)
        {
            return Client.SendTextMessage(receiveId, OwnUserInfo.Id, text);
        }

        /// <summary>
        /// 发送文本广播
        /// </summary>
        /// <param name="text">广播文本内容</param>
        /// <returns>True 成功，False 失败。</returns>
        public bool SendTextBroadcast(string text)
        {
            return Client.BroadcastTextMessage(OwnUserInfo.Id, text);
        }

        /// <summary>
        /// 向指定联系人发送文件
        /// </summary>
        /// <param name="receiveId">接收用户 Id，NULL 表示服务器</param>
        /// <param name="fileName">文件名称</param>
        /// <exception cref="ArgumentNullException">如果文件名为 NULL，将引发此异常</exception>
        /// <exception cref="ArgumentException">如果文件不存在，则将引发此异常</exception>
        public void SendFile(string receiveId, string fileName)
        {
            if (fileName == null) throw new ArgumentNullException("fileName");
            if (!File.Exists(fileName))
                throw new ArgumentException("fileName");

            var transferFileId = Helpers.GetGuidToN();
            var fileInfo = new FileInfo(fileName);
            var sendFileInfo = new TransferFileInfo
            {
                SourceFileName = fileName,
                Request = true,
                SendId = transferFileId,
                ReceiveUserId = receiveId,
                SendUserId = OwnUserInfo.Id,
                Size = fileInfo.Length,
                IsSend = true
            };
            TransferFileInfos.Add(sendFileInfo);

            ThreadPool.QueueUserWorkItem(CreateTransferFileClientForSend, transferFileId);
        }

        private void CreateTransferFileClientForSend(object obj)
        {
            var transferFileId = (string)obj;
            var transferFileInfo = TransferFileInfos.Find(t => t.SendId == transferFileId);

            transferFileInfo.FileHashCode = Helpers.GetFileHashCode(transferFileInfo.SourceFileName);

            var transferFileClient = new BaseCommunication();
            transferFileInfo.Client = transferFileClient;
            transferFileClient.OwnUserInfoReceived += OnTransferFileClientOwnUserInfoReceivedForSend;
            transferFileClient.InitializeBaseCommunication(CommunicationArgs);
            transferFileClient.Connect();
            transferFileClient.SendUserInfoMessage(new ContactsInfo { Id = transferFileId, Name = transferFileInfo.SendUserId, TransferFile = true, LoginTime = DateTime.Now });
        }

        /// <summary>
        /// 传输文件客户端收到自己的用户信息
        /// <para>用于发送文件的客户端</para>
        /// </summary>
        /// <param name="obj">自己的 Id</param>
        private void OnTransferFileClientOwnUserInfoReceivedForSend(string obj)
        {
            var transferFileInfo = TransferFileInfos.Find(f => f.SendId == obj);
            if (transferFileInfo != null)
            {
                Client.SendRequestSendFileMessage(transferFileInfo.ReceiveUserId, transferFileInfo.SendUserId, transferFileInfo);
            }
        }

        private void OnTransferFileClientFileMessageRecived(object sender, FileMessageReceivedEventArgs e)
        {
            using (var fs = new FileStream(e.TransferFileInfo.FileName, FileMode.Append, FileAccess.Write, FileShare.Write))
            {
                fs.Write(e.Bytes, 0, e.Bytes.Length);
                var speed = (decimal)e.TransferFileInfo.CurrentSize / e.TransferFileInfo.Size * 100;
                if (ReciveFileProgressChanged != null)
                {
                    ReciveFileProgressChanged.Invoke(this, new ProgressIsChangedEventArgs((int)speed, e.TransferFileInfo));
                }
                if (fs.Length < e.TransferFileInfo.Size)
                    return;

                var fileclient = TransferFileInfos.Find(f => f.ReceiveId == e.TransferFileInfo.ReceiveId);

                fileclient.IsOver = true;
                fileclient.OverTime = DateTime.Now;
                fs.Flush();
                fs.Close();
                fs.Dispose();
                var hashCode = Helpers.GetFileHashCode(e.TransferFileInfo.FileName);
                e.IsComplete = hashCode == e.TransferFileInfo.FileHashCode;
                if (ReciveFileComplete != null)
                {
                    ReciveFileComplete.Invoke(this, e);
                }
            }
        }

        /// <summary>
        /// 停止客户端
        /// </summary>
        public void Stop()
        {
            Client.Close();
            Client.InitializeBaseCommunication(CommunicationArgs);
        }

        /// <summary>
        /// 更新用户列表
        /// <para>更新成功则将触发 UpdateUsersComplete 事件</para>
        /// </summary>
        public void UpdateUsers()
        {
            Client.SendRequestUsersMessage();
        }

        private void OnClientReciveMessage(object sender, TextMessageReceivedEventArgs e)
        {
            if (TextMessageReceived != null)
            {
                TextMessageReceived.Invoke(this, e);
            }
        }

        private void OnClientUpdateContactsComplete()
        {
            if (UpdateUsersComplete != null)
                UpdateUsersComplete.Invoke();
        }

        /// <summary>
        /// 收到文本消息事件
        /// </summary>
        [Description("当收到文本消息时发生")]
        public event EventHandler<TextMessageReceivedEventArgs> TextMessageReceived;

        /// <summary>
        /// 请求发送文件事件
        /// </summary>
        [Description("当收到请求发送文件时发生")]
        public event EventHandler<FileMessageReceivedEventArgs, bool> RequestSendFile;

        /// <summary>
        /// 接收文件成功事件
        /// </summary>
        [Description("当接收文件成功时发生")]
        public event EventHandler<FileMessageReceivedEventArgs> ReciveFileComplete;

        /// <summary>
        /// 发送文件成功事件
        /// </summary>
        [Description("当发送文件成功时发生")]
        public event EventHandler<FileMessageReceivedEventArgs> SendFileComplete;

        /// <summary>
        /// 发送文件进度改变事件
        /// </summary>
        [Description("当发送文件进度改变时发生")]
        public event EventHandler<ProgressIsChangedEventArgs> SendFileProgressChanged;

        /// <summary>
        /// 接收文件进度改变事件
        /// </summary>
        [Description("当接收文件进度改变时发生")]
        public event EventHandler<ProgressIsChangedEventArgs> ReciveFileProgressChanged;

        /// <summary>
        /// 运行状态改变事件
        /// </summary>
        [Description("当运行状态改变时发生")]
        public event Action<bool> IsRuningChanged;

        /// <summary>
        /// 更新好友列表成功
        /// </summary>
        [Description("当更新好友列表成功时发生")]
        public event Action UpdateUsersComplete;

        /// <summary>
        /// 用户上线事件
        /// </summary>
        [Description("当有新用户上线时发生")]
        public event EventHandler<ContactsInfoEventArgs> ContactsConnected;

        /// <summary>
        /// 用户下线事件
        /// </summary>
        [Description("当有新用户下线时发生")]
        public event EventHandler<ContactsInfoEventArgs> ContactsOffline;

        /// <summary>
        /// 传送文件用户下线事件
        /// </summary>
        [Description("当传送文件用户下线时发生")]
        public event EventHandler<ContactsInfoEventArgs> TransferFileUserOffline;

        /// <summary>
        /// 收到广播文本事件
        /// </summary>
        [Description("当收到广播文本时发生")]
        public event EventHandler<TextMessageReceivedEventArgs> BroadcastTextMessageReceived;

        /// <summary>
        /// 联系人同意接收文件事件
        /// </summary>
        [Description("当联系人回复同意接收文件时发生")]
        public event EventHandler<FileMessageReceivedEventArgs> ContactsAgreeReciveFile;
    }
}